package com.hzw.saas.web.admin.hpc.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hzw.saas.api.hpc.IHpcAccountDetailService;
import com.hzw.saas.api.hpc.IHpcAccountRecordService;
import com.hzw.saas.api.hpc.IHpcAccountUserService;
import com.hzw.saas.api.hpc.IHpcContractService;
import com.hzw.saas.api.hpc.bo.HpcAccountBo;
import com.hzw.saas.api.hpc.bo.HpcContractBO;
import com.hzw.saas.api.hpc.bo.HpcContractStructBO;
import com.hzw.saas.api.hpc.enums.HpcContractStatus;
import com.hzw.saas.api.hpc.enums.HpcContractType;
import com.hzw.saas.api.hpc.enums.HpcProviderType;
import com.hzw.saas.api.hpc.query.*;
import com.hzw.saas.api.property.IPropertyService;
import com.hzw.saas.api.property.enmus.SysDictKeyEnum;
import com.hzw.saas.api.rbac.constants.SaasAuth;
import com.hzw.saas.api.user.ICustomerLevelService;
import com.hzw.saas.api.user.ICustomerService;
import com.hzw.saas.api.user.bo.CustomerBO;
import com.hzw.saas.api.user.bo.CustomerLevelBO;
import com.hzw.saas.common.config.annotation.SysLog;
import com.hzw.saas.common.config.aspect.RedisLockAspect;
import com.hzw.saas.common.config.exception.DefaultSaasException;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.config.util.ExcelUtils;
import com.hzw.saas.common.config.util.PageParam;
import com.hzw.saas.common.config.util.PageUtils;
import com.hzw.saas.common.security.utils.SecurityUtils;
import com.hzw.saas.common.util.Arith;
import com.hzw.saas.common.util.Json;
import com.hzw.saas.common.util.MapperUtil;
import com.hzw.saas.service.hpc.service.IHpcAccountSlurmService;
import com.hzw.saas.service.hpc.service.IQCloudHpcAccountService;
import com.hzw.saas.service.hpc.service.bo.SlurmAccountBO;
import com.hzw.saas.service.hpc.service.query.QCloudHpcAccountQuery;
import com.hzw.saas.web.admin.hpc.dto.*;
import com.hzw.saas.web.admin.hpc.param.*;
import com.hzw.saas.web.admin.security.PermissionService;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @Description
 * @Date 2021/7/16 10:43 上午
 * @Created by sonam
 */
@Slf4j
@Api(tags = "HPC/HPC合同")
@RestController
@RequiredArgsConstructor
@RequestMapping("/hpc/contract")
public class HpcContractController {

    @Resource(name = PermissionService.BEAN_NAME)
    private PermissionService permissionService;
    @Resource(name = SaasAuth.BEAN_NAME)
    private SaasAuth saasAuth;
    @Resource(name = IPropertyService.SYS_DICT_BEAN)
    private IPropertyService<SysDictKeyEnum> propertyService;
    private final ICustomerService customerService;
    private final ICustomerLevelService levelService;
    private final IHpcContractService contractService;
    private final IHpcAccountSlurmService slurmService;
    private final IHpcAccountUserService accountUserService;
    private final IQCloudHpcAccountService qcHpcAccountService;
    private final IHpcAccountRecordService accountRecordService;
    private final IHpcAccountDetailService accountDetailService;

    private final RedissonClient redissonClient;
    private final ApplicationEventPublisher publisher;

    @PostMapping("/")
    @ApiOperationSort(1)
    @SysLog(operation = "合同登记/更新", mode = 3)
    @ApiOperation(value = "合同登记/更新", notes = "当合同ID为空则为更新，否则登记，已绑定的合同不能修改")
    public ResponseEntity<HpcContractBO> contractRegistration(@Validated @RequestBody ContractRegistrationParam contractRegistrationParam) {
        ContractRegistrationQuery contractRegistrationQuery = MapperUtil.nf().map(contractRegistrationParam, ContractRegistrationQuery.class);
        contractRegistrationQuery.setHpcContractType(HpcContractType.FORMAL);
        contractRegistrationQuery.setProviderCode(contractRegistrationParam.getProviderCode());
        HpcContractBO contractBO = contractService.registrationOrUpdateContract(contractRegistrationQuery);
        return ResponseEntity.ok(contractBO);
    }

    @ApiOperationSort(2)
    @GetMapping("/{contractId}")
    @SysLog(operation = "合同详情")
    @ApiOperation(value = "合同详情", notes = "合同详情")
    public ResponseEntity<HpcContractDTO> contractRegistration(@PathVariable String contractId) {
        HpcContractBO hpcContractBO = contractService.getById(contractId);
        HpcContractDTO hpcContractDTO = MapperUtil.nf().map(hpcContractBO, HpcContractDTO.class);
        if (Objects.nonNull(hpcContractBO)) {
            Double consumeCoreTime = accountDetailService.getHpcAccountCoreTimeSum(hpcContractBO.getHpcAccountRecordId());
            hpcContractDTO.setConsumeCoreTime(consumeCoreTime);
        }
        return ResponseEntity.ok(hpcContractDTO);
    }

    @ApiOperationSort(3)
    @PostMapping("/apply")
    @SysLog(operation = "合同使用", mode = 3)
    @ApiOperation(value = "合同使用(正常流程)", notes = "正常流程，先登记合同，后绑定HPC账号")
    public ResponseEntity<Void> contractTrial(@RequestBody @Validated ContractUsableQuery contractUsableQuery) {
        contractService.contractUsable(contractUsableQuery);
        return ResponseEntity.ok().build();
    }

    @ApiOperationSort(4)
    @PostMapping("/bound")
    @SysLog(operation = "合同绑定", mode = 3)
    @ApiOperation(value = "合同绑定(续期、历史分配的HPC账号绑定合同)", notes = "用于续期或者历史分配的HPC账号")
    public ResponseEntity<HpcContractBO> createContractByHpcAccountUserId(@Validated @RequestBody CreateHpcContractParam createHpcContractParam) {
        CreateHpcContractQuery createHpcContractQuery = MapperUtil.nf().map(createHpcContractParam, CreateHpcContractQuery.class);
        return ResponseEntity.ok(contractService.boundContract(createHpcContractQuery));
    }

    @ApiOperationSort(5)
    @PostMapping("/trial")
    @SysLog(operation = "试用合同", mode = 3)
    @ApiOperation(value = "试用合同", notes = "试用合同，根据合同模板，自动创建合同以及绑定HPC账号")
    public ResponseEntity<Void> contractTrial(@RequestBody @Valid TrialContractParam trialContractParam) {
        AssertUtil.assertThrow("合同模板数据异常，请重新选择合同模版", !checkTrialContractParam(trialContractParam));
        // 检查队列名是否有效
        String slurmAccount = trialContractParam.getSlurmAccount();
        if(Objects.equals(HpcProviderType.QY.value(), trialContractParam.getProviderCode()) && StrUtil.isNotBlank(slurmAccount)) {
            List<SlurmAccountBO> slurmAccounts = slurmService.getSlurmAccounts();
            AssertUtil.assertThrow("队列不存在，请重新选择", slurmAccounts.stream().filter(slurmAccountBO -> Objects.equals(slurmAccount, slurmAccountBO.getSlurmAccount())).collect(Collectors.toList()).size() == 0);
        }
        TrialContractQuery trialContractQuery = MapperUtil.nf().map(trialContractParam, TrialContractQuery.class);
        RLock lock = redissonClient.getLock(RedisLockAspect.REDISSON_LOCK_PREFIX + "BoundHpcAccount:" + trialContractParam.getHpcAccountId());
        AssertUtil.assertThrow("操作频繁，稍后重试", lock.isLocked());
        contractService.trialContract(trialContractQuery);
        return ResponseEntity.ok().build();
    }

    private boolean checkTrialContractParam(TrialContractParam trialContractParam) {
        if (Objects.isNull(trialContractParam)) {
            return false;
        }

        ResponseEntity<List<HpcContractTemplateDTO>> listResponseEntity = listContractTemplateList();
        for (HpcContractTemplateDTO hpcContractTemplate : listResponseEntity.getBody()) {
            if (Objects.equals(hpcContractTemplate.getCoreTime(), trialContractParam.getCoreTime()) &&
                Objects.equals(hpcContractTemplate.getExpireDay(), trialContractParam.getExpireDay())) {
                return true;
            }
        }

        return false;
    }

    @DeleteMapping("/")
    @ApiOperationSort(6)
    @SysLog(operation = "删除合同", mode = 3)
    @ApiOperation(value = "删除合同", notes = "删除合同，已生效的合同不能删除")
    public ResponseEntity<Void> deleteContract(@RequestBody List<String> contractIds) {
        contractService.deleteContracts(contractIds, false);
        return ResponseEntity.ok().build();
    }

    @DeleteMapping("/unbound")
    @ApiOperationSort(7)
    @SysLog(operation = "解绑合同", mode = 3)
    @ApiOperation(value = "解绑合同", notes = "解绑合同，会扣除对应HPC的核时")
    public ResponseEntity<Void> unboundContract(@RequestBody List<String> contractIds, @ApiParam(value = "是否强制解绑") @RequestParam(required = false) Boolean forceBound) {
        contractService.unboundContract(contractIds, null != forceBound && forceBound);
        return ResponseEntity.ok().build();
    }

    @ApiOperationSort(8)
    @GetMapping("/list")
    @SysLog(operation = "合同查询")
    @ApiOperation(value = "合同查询", notes = "合同查询")
    public ResponseEntity<IPage<HpcContractDTO>> listContract(PageParam pageParam, HpcContractGetParam hpcContractGetParam) {
        IPage<HpcContractBO> contractBOS = contractService.listByUserIds(pageParam, hpcContractGetParam.getUserIds(), hpcContractGetParam.getKeyword(), hpcContractGetParam.getType(), hpcContractGetParam.getContractType(),
            hpcContractGetParam.getProviderCodes());
        return ResponseEntity.ok(PageUtils.convert(contractBOS, HpcContractDTO.class));
    }

    @ApiOperationSort(9)
    @GetMapping("/search")
    @SysLog(operation = "合同报表查询")
    @ApiOperation(value = "合同报表查询", notes = "合同报表查询")
    public ResponseEntity<IPage<HpcContractStructDTO>> listContractsExport(PageParam pageParam, SearchContractParam searchContractParam) {
        boolean queryAll = permissionService.hasAny(saasAuth.CONTRACT_LIST_ALL);
        boolean querySelf = permissionService.hasAny(saasAuth.CONTRACT_LIST);
        String creatorId;
        if (queryAll) {
            creatorId = null;
        } else if (querySelf) {
            creatorId = SecurityUtils.getUser().getUserId();
        } else {
            throw DefaultSaasException.build("无权限").status(HttpStatus.FORBIDDEN);
        }
        // 分页查询合同对应的USER-ID列表
        SearchContractQuery query = MapperUtil.nf().map(searchContractParam, SearchContractQuery.class);
        query.setSysUserId(creatorId);
        IPage<HpcContractStructBO> hpcContractStructBOIPage = contractService.listContractPageByUserIds(pageParam, query);
        // 构造合同报表数据结构
        Page<HpcContractStructDTO> result = buildHpcContractStructDTO(pageParam, hpcContractStructBOIPage);
        return ResponseEntity.ok(result);
    }

    @ApiOperationSort(10)
    @GetMapping("/template")
    @SysLog(operation = "获取合同模板列表")
    @ApiOperation(value = "获取合同模板列表", notes = "获取合同模板列表")
    public ResponseEntity<List<HpcContractTemplateDTO>> listContractTemplateList() {
        String propertyValue = propertyService.getPropertyValue(SysDictKeyEnum.TRIAL_TEMPLATE_KEY);
        try {
            List<HpcContractTemplateDTO> hpcContractTemplates = Json.parseArray(propertyValue, HpcContractTemplateDTO[].class);
            return ResponseEntity.ok(hpcContractTemplates);
        } catch (Exception e) {
            return ResponseEntity.ok(new ArrayList<>());
        }
    }

    /**
     * 构造合同报表数据结构
     */
    private Page<HpcContractStructDTO> buildHpcContractStructDTO(PageParam pageParam, IPage<HpcContractStructBO> hpcContractStructBOIPage) {
        Page<HpcContractStructDTO> result = pageParam.convert();
        result.setTotal(hpcContractStructBOIPage.getTotal());
        result.setRecords(new ArrayList<>());
        hpcContractStructBOIPage.getRecords().forEach(hpcContractStructBO -> {
            HpcContractStructDTO contractStructDTO = MapperUtil.nf().map(hpcContractStructBO, HpcContractStructDTO.class);

            CustomerBO customerBO = customerService.getCustomerByUserId(contractStructDTO.getUserId());
            if (customerBO != null) {
                // 设置用户注册时间
                contractStructDTO.setUserRegisterTime(customerBO.getCreateTime());
                // 设置等级
                String customerLevel = customerBO.getCustomerLevel();
                contractStructDTO.setLevelId(customerLevel);
                CustomerLevelBO customerLevelBO = levelService.getLevelById(customerLevel);
                if (customerLevelBO != null) {
                    contractStructDTO.setLevelText(customerLevelBO.getLevelText());
                }
            }

            List<HpcContractDetailDTO> hpcContractDetailDTOS = new ArrayList<>();
            List<HpcContractBO> hpcContracts = hpcContractStructBO.getHpcContracts();
            hpcContracts.forEach(hpcContract -> {
                if (hpcContract.getHpcAccount() == null) {
                    hpcContract.setHpcAccount(StrUtil.EMPTY);
                    hpcContract.setHpcAccountId(StrUtil.EMPTY);
                    hpcContract.setHpcAccountRecordId(StrUtil.EMPTY);
                }
            });

            Map<String, List<HpcContractBO>> hpcContractMapping = hpcContracts.stream().collect(Collectors.groupingBy(HpcContractBO::getHpcAccount));
            hpcContractMapping.forEach((hpcAccount, contractBOS) -> {
                HpcContractDetailDTO contractDetailDTO = new HpcContractDetailDTO();
                hpcContractDetailDTOS.add(contractDetailDTO);

                // 合同信息
                List<HpcContractDTO> hpcContractDTOS = MapperUtil.nf().mapAsList(contractBOS, HpcContractDTO.class);
                // 合同根据过期时间倒序
                hpcContractDTOS.sort((a, b) -> DateUtil.compare(a.getExpireTime(), b.getExpireTime()));

                // 四舍五入
                hpcContractDTOS.forEach(hpcContractDTO -> {
                    hpcContractDTO.setContractAmount(NumberUtil.round(hpcContractDTO.getContractAmount(), 2).doubleValue());
                    hpcContractDTO.setCoreTime(NumberUtil.round(hpcContractDTO.getCoreTime(), 2).doubleValue());
                });
                contractDetailDTO.setHpcContracts(hpcContractDTOS);

                String hpcAccountId = contractBOS.get(0).getHpcAccountId();
                String hpcAccountRecordId = contractBOS.get(0).getHpcAccountRecordId();
                if (StrUtil.isBlank(hpcAccountId) || StrUtil.isBlank(hpcAccountRecordId)) {
                    // 未履行
                    return;
                }

                contractDetailDTO.setHpcAccount(hpcAccount);
                contractDetailDTO.setHpcAccountId(hpcAccountId);
                contractDetailDTO.setHpcAccountRecordId(hpcAccountRecordId);
                // 计算HPC账号消耗量
                Double consumeCoreTime = accountDetailService.getHpcAccountCoreTimeSum(hpcAccountRecordId);
                contractDetailDTO.setConsumeCoreTime(NumberUtil.round(consumeCoreTime, 2).doubleValue());

                double value = consumeCoreTime;
                // 重新设置履行中的 状态文本
                for (HpcContractDTO hpcContractDTO : hpcContractDTOS) {
                    HpcContractStatus hpcContractStatus = HpcContractStatus.instance(hpcContractDTO.getStatusCode());
                    // 履行中
                    if (hpcContractStatus == HpcContractStatus.PERFORM) {
                        Double coreTime = hpcContractDTO.getCoreTime();
                        value = Arith.sub(coreTime, value);
                        if (value > 0) {
                            break;
                        }
                        // 设置履行完成
                        hpcContractDTO.setStatusCode(HpcContractStatus.PERFORM_FINISHED.value());
                        hpcContractDTO.setStatusText(HpcContractStatus.PERFORM_FINISHED.text());
                        value = Math.abs(value);
                    }
                }
            });
            contractStructDTO.setContractDetails(hpcContractDetailDTOS);
            result.getRecords().add(contractStructDTO);
        });
        return result;
    }

    @SneakyThrows
    @ApiOperationSort(11)
    @GetMapping("/export")
    @SysLog(operation = "导出合同报表", mode = 3)
    @ApiOperation(value = "导出合同报表", notes = "导出合同报表")
    @ApiResponses({
        @ApiResponse(code = 200, message = "成功"),
        @ApiResponse(code = 204, message = "无数据")
    })
    public ResponseEntity<Void> exportContract(SearchContractParam searchContractParam, HttpServletResponse response) {
        ResponseEntity<IPage<HpcContractStructDTO>> iPageResponseEntity = listContractsExport(PageParam.getBigPageParam(), searchContractParam);
        IPage<HpcContractStructDTO> responseEntityBody = iPageResponseEntity.getBody();

        if (responseEntityBody.getTotal() == 0) {
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
        }

        List<HpcContractStructDTO> records = responseEntityBody.getRecords();
        List<HpcExportHpcContractDTO> exportHpcContractDTOS = new ArrayList<>();
        records.forEach(record -> {
            List<HpcContractDetailDTO> contractDetails = record.getContractDetails();
            if (CollectionUtil.isEmpty(contractDetails)) {
                return;
            }
            contractDetails.forEach(contractDetail -> {
                List<HpcContractDTO> hpcContracts = contractDetail.getHpcContracts();
                if (CollectionUtil.isEmpty(hpcContracts)) {
                    return;
                }
                double consumeCoreTime = contractDetail.getConsumeCoreTime();
                for (HpcContractDTO hpcContract : hpcContracts) {
                    HpcExportHpcContractDTO dto = new HpcExportHpcContractDTO();
                    BeanUtils.copyProperties(record, dto);
                    BeanUtils.copyProperties(contractDetail, dto);
                    BeanUtils.copyProperties(hpcContract, dto);
                    dto.setConsumeCoreTime(consumeCoreTime);
                    HpcContractType instance = HpcContractType.instance(hpcContract.getContractType());
                    dto.setType(instance == null ? HpcContractType.FORMAL.text() : instance.text());
                    dto.setUserRegisterDate(DateUtil.formatDate(record.getUserRegisterTime()));
                    exportHpcContractDTOS.add(dto);
                }
            });
        });
        ExcelUtils.exportExcel(exportHpcContractDTOS, "合同报表", "contract", HpcExportHpcContractDTO.class, "合同报表", response);
        return ResponseEntity.ok().build();
    }

    @Transactional
    @ApiOperationSort(12)
    @PostMapping("/createAndBound")
    @SysLog(operation = "指定青云HPC账号并绑定正式合同", mode = 3)
    @ApiOperation(value = "指定青云HPC账号并绑定正式合同", notes = "创建HPC账号并绑定正式合同")
    public ResponseEntity<Void> createAndBound(@RequestBody @Valid QingCloudContractUsableQuery qingCloudContractUsableQuery) {
        contractService.createAndBoundQCloudHpc(qingCloudContractUsableQuery);
        return ResponseEntity.ok().build();
    }

    @Transactional
    @ApiOperationSort(13)
    @PostMapping("/createAndTrial")
    @SysLog(operation = "指定青云HPC账号并绑定试用合同", mode = 3)
    @ApiOperation(value = "指定青云HPC账号并绑定试用合同", notes = "创建青云HPC账号并绑定试用合同")
    public ResponseEntity<Void> createAndTrial(@RequestBody @Valid QingCloudContractTrialQuery trialQuery) {
        // 创建青云账号
        QCloudHpcAccountQuery qCloudHpcAccountQuery = new QCloudHpcAccountQuery();
        qCloudHpcAccountQuery.setSlurmAccount(trialQuery.getSlurmAccount());
        qCloudHpcAccountQuery.setHpcAccountName(trialQuery.getHpcAccount());
        qCloudHpcAccountQuery.setCoreTime(trialQuery.getCoreTime());
        HpcAccountBo hpcAccountBo = qcHpcAccountService.createOrUpdateQCloudAccount(qCloudHpcAccountQuery);
        // 账号试用
        TrialContractParam trialContractParam = new TrialContractParam();
        trialContractParam.setUserId(trialQuery.getUserId());
        trialContractParam.setCoreTime(trialQuery.getCoreTime());
        trialContractParam.setExpireDay(trialQuery.getExpireDay());
        trialContractParam.setProviderCode(HpcProviderType.QY.value());
        trialContractParam.setHpcAccountId(hpcAccountBo.getHpcAccountId());
        trialContractParam.setSlurmAccount(trialQuery.getSlurmAccount());
        this.contractTrial(trialContractParam);
        return ResponseEntity.ok().build();
    }


}
